<?php
if (!function_exists('GetVersion')) {
    /**
     * 获取当前框架版本
     * @return string
     */
    function GetVersion()
    {
        return app()->version();
    }
}

if (!function_exists('DieDump')) {
    /**
     * 断点输出
     * @param $var 输出的变量
     * @param bool $isJson
     */
    function DieDump($var, $isJson = false)
    {
        if ($isJson) {
            die(json_encode($var, 256));
        } else {
            var_dump($var);
            die();
        }
    }
}

if (!function_exists('CreateToken')) {
    /**
     * 创建用户TOKEN
     * @param int $userId 用户ID
     * @return array
     * @throws Exception
     */
    function CreateToken($userId = 0)
    {
        list($usec, $sec) = explode(" ", microtime());
        $timeSec = ((float)$usec + (float)$sec);
        $time = uniqid($userId) . $timeSec;
        $rand = random_int(0, 1000);
        $token = md5(md5($time) . md5($rand));
        $cryptToken = \Illuminate\Support\Facades\Crypt::encryptString($token);
        return compact('token', 'cryptToken');
    }
}

if (!function_exists('ApiReturn')) {
    /**
     * 获取API返回固定格式
     * @param string $status
     * @param string $info
     * @param array $data
     * @param int $code
     * @return false|string
     * @throws Exception
     */
    function ApiReturn($code = RETURN_SUCCESS, $info = '', $data = [], $status = '')
    {
        if (is_array($code)) {
            try {
                return json_encode([
                    'status' => $code['code'] == RETURN_SUCCESS ? "YES" : "NO",
                    'info' => $code['msg'],
                    'data' => $code['data'],
                    'code' => $code['code']
                ], 256);
            } catch (\Throwable $e) {
                throw new \Exception('API RETURN FORMAT FAIL', RETURN_FAIL);
            }
        }
        $status = $status !== '' ? $status : ($code == RETURN_SUCCESS ? "YES" : "NO");
        return json_encode(compact('status', 'info', 'data', 'code'), 256);
    }
}

if (!function_exists('ModelReturn')) {
    /**
     * 模型内部返回固定格式
     * @param int $code
     * @param string $msg
     * @param array $data
     * @return array
     */
    function ModelReturn($code = RETURN_SUCCESS, $msg = '', $data = [])
    {
        $code = (int)$code;
        return compact('code', 'msg', 'data');
    }
}

if (!function_exists('ArrToSql')) {
    /**
     * 数组转换为SQL查询语句的in条件
     * @param $arr
     * @return string
     */
    function ArrToSql($arr)
    {
        $str = sprintf("('%s')", implode("','", $arr));
        return $str;
    }
}

if (!function_exists('HandleLog')) {
    /**
     * 获取处理所有日志信息log对象
     * @return \App\Common\ScarecrowProvider\ScarecrowDoLogProvider|string
     * @throws Exception
     */
    function HandleLog()
    {
        return \App\Common\ScarecrowProvider\ScarecrowDoLogProvider::getExample();
    }
}

if (!function_exists('DbObjectToArr')) {
    /**
     * DB返回的对象数据转为数组
     * @param $data 数据库返回对象
     * @return mixed
     */
    function DbObjectToArr($data)
    {
        return json_decode(json_encode($data), true);
    }
}

if (!function_exists('ReadAllFiles')) {
    /**
     * 获取文件夹下的所有文件名称（文件夹与文件有关联关系）
     * @param $dirPath 文件夹路径
     * @return array
     */
    function ReadAllFiles($dirPath)
    {
        $result = [];
        $handle = opendir($dirPath);
        if ($handle) {
            while (($file = readdir($handle)) !== false) {
                if ($file != '.' && $file != '..') {
                    $cur_path = $dirPath . '/' . $file;
                    if (is_dir($cur_path)) {
                        $result['dir'][$file] = ReadAllFiles($cur_path);
                    } else {
                        $result['file'][$file] = $cur_path;
                    }
                }
            }
            closedir($handle);
        }
        $result['basePath'] = $dirPath;
        return $result;
    }
}

if (!function_exists('ReadAllDirAndFiles')) {
    /**
     * 获取文件夹下的所有文件名称（文件夹与文件无关联关系）
     * @param string $basePath 文件夹路径
     * @return array|bool
     */
    function ReadAllDirAndFiles($basePath = '')
    {
        $allDir = [];
        $allFile = [];

        $basePath = realpath($basePath);
        if ($basePath === false) {
            return false;
        }

        $allDir[] = $basePath;
        $allHandleDir = $allDir;

        while (!empty($allHandleDir)) {
            $basePathTemp = array_pop($allHandleDir);
            $allObj = scandir($basePathTemp);

            foreach ($allObj as $item) {
                if ($item == '.' || $item == '..') {
                    continue;
                }

                $path = $basePathTemp . '/' . $item;
                if (is_dir($path)) {
                    $allDir[] = $path;
                    $allHandleDir[] = $path;
                } else {
                    $allFile[] = $path;
                }
            }
        }

        return [
            'dir' => $allDir,
            'file' => $allFile
        ];
    }
}

if (!function_exists('CurlPost')) {
    /**
     * CURL POST请求
     * @param $url 请求路径
     * @param $data 请求数据,不同的请求方式使用不同的格式，如果需要字符串而传入了对象或数组则会自动调用json序列化为字符串，如果contentType='multipart/form-data'时如果需要传输文件，则请在参数中以media为key,值为数组，数组中存放上传文件的二进制数据,数组中可以有多个文件 例如: param=['p1'=>'t1','media'=>['file1'=>['fileName'=>'文件名字', 'content'=>file_get_content($path1)], 'file2'=>['fileName'=>'文件名字', 'content'=>file_get_content($path2)]]]
     * @param array $header 每一个元素就是一个header项,例如： '"Content-Type: application/x-www-form-urlencoded;'
     * @param bool $isSsl 是否进行SSL证书验证,默认FALSE
     * @param string $contentType 数据传输类型,支持:multipart/form-data;application/json;application/x-www-form-urlencoded;text/html;text/plain;
     * @param bool $isOutHeader 是否解析响应头,如果传入false则会解析header的key=>value到响应结果中
     * @param array $certs 如果需要验证证书则传入证书地址
     * @return array ['code'=>200, 'msg'=>'success', 'response'=>['header'=>[], 'data'=>""]]
     */
    function CurlPost($url, $data, $header = [], $isSsl = false, $contentType = "application/x-www-form-urlencoded", $isOutHeader = false, $certs = ['apiclient_cert' => '', 'apiclient_key' => '', 'cacert' => ''])
    {
        // 所有允许访问的类型
        $contentTypeList = [
            "multipart/form-data" => function ($param) {
                $delimiter = uniqid();
                $data = '';
                $eol = "\r\n";
                $mediaList = array_key_exists('media', $param) ? (array)$param['media'] : [];
                unset($param['media']);
                $fromDataList = [];
                foreach ($param as $key => $value) {
                    $fromDataList[$key] = is_string($value) ? $value : json_encode($value);
                }

                foreach ($fromDataList as $name => $content) {
                    $data .= "--" . $delimiter . $eol
                        . 'Content-Disposition: form-data; name="' . $name . "\"" . $eol . $eol
                        . $content . $eol;
                }

                foreach ($mediaList as $key => $value) {
                    $data .= "--" . $delimiter . $eol
                        . 'Content-Disposition: form-data; name="' . $key . '"; filename="' . $value['fileName'] . '"' . $eol
                        . 'Content-Type:application/octet-stream' . $eol . $eol;
                    $data .= $value['content'] . $eol;
                    $data .= "--" . $delimiter . "--" . $eol;
                }

                return ['delimiter' => $delimiter, 'data' => $data];
            },
            "application/json" => function ($data) {
                return json_encode($data);
            },
            "application/x-www-form-urlencoded" => function ($data) {
                return is_string($data) ? $data : http_build_query($data);
            },
            "text/html" => function ($data) {
                return is_string($data) ? $data : json_encode($data);
            },
            "text/plain" => function ($data) {
                return is_string($data) ? $data : json_encode($data);
            }
        ];

        if (!array_key_exists($contentType, $contentTypeList)) {
            return ['code' => 500, 'msg' => '请求类型不支持', 'response' => ['header' => [], 'data' => ""]];
        }

        $data = $contentTypeList[$contentType]($data);
        $isOutHeader = (boolean)$isOutHeader;
        $ch = curl_init();
        $timeout = 600;

        if ($contentType == "multipart/form-data") {
            $header[] = "Content-Type: multipart/form-data; boundary=" . $data['delimiter'];
            $data = $data['data'];
        } else {
            $header[] = "Content-Type: " . $contentType . "; charset=utf-8";
        }

        $header[] = "Content-Length: " . strlen($data);
        $header[] = 'Cache-Control: no-cache';
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        $isOutHeader ? curl_setopt($ch, CURLOPT_HEADER, true) : curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
        if ($isSsl) {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);//证书检查
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'pem');
//			curl_setopt($ch,CURLOPT_SSLCERT,dirname(__FILE__).'/../cert/apiclient_cert.pem');
            curl_setopt($ch, CURLOPT_SSLCERT, $certs['apiclient_cert']);
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'pem');
//			curl_setopt($ch,CURLOPT_SSLKEY,dirname(__FILE__).'/../cert/apiclient_key.pem');
            curl_setopt($ch, CURLOPT_SSLCERT, $certs['apiclient_key']);
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'pem');
//			curl_setopt($ch,CURLOPT_CAINFO,dirname(__FILE__).'/../cert/cacert.pem');
            curl_setopt($ch, CURLOPT_SSLCERT, $certs['cacert']);
        } else {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        }

        try {
            $reponse = curl_exec($ch);
            if (curl_errno($ch)) {
                throw new \Exception(curl_error($ch), 0);
            } else {
                $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                if (!in_array($httpStatusCode, [200, 201])) {
                    throw new \Exception($reponse, $httpStatusCode);
                }
            }
            curl_close($ch);

            if ($isOutHeader) {
                $headerData = [];
                list($headerStr, $responseData) = explode("\r\n\r\n", $reponse, 2);
                $headerDataList = explode("\r\n", $headerStr);
                $isStart = true;
                foreach ($headerDataList as $headerLine) {
                    if ($isStart) {
                        $isStart = false;
                        continue;
                    }

                    list($headerKey, $headerValue) = explode(":", $headerLine);
                    if (array_key_exists(trim($headerKey))) {
                        $headerData[trim($headerKey)] = $headerData[trim($headerKey)]. "," . trim($headerValue);
                    } else {
                        $headerData[trim($headerKey)] = trim($headerValue);
                    }
                }
            } else {
                $headerData = [];
                $responseData = $reponse;
            }

            return ['code' => $httpStatusCode, 'msg' => 'success', 'response' => ['header' => $headerData, 'data' => $responseData]];
        } catch (\Throwable $exception) {
            curl_close($ch);
            return ['code' => $exception->getCode(), 'msg' => $exception->getMessage(), 'response' => ['header' => [], 'data' => ""]];
        }
    }
}

if (!function_exists('CurlGet')) {
    /**
     * CURL GET请求
     * @param $url 请求路径
     * @param $data 请求数据,不同的请求方式使用不同的格式，如果需要字符串而传入了对象或数组则会自动调用json序列化为字符串
     * @param array $header 每一个元素就是一个header项,例如： '"Content-Type: application/x-www-form-urlencoded;'
     * @param bool $isSsl 是否进行SSL证书验证,默认FALSE
     * @param bool $isOutHeader 是否解析响应头,如果传入false则会解析header的key=>value到响应结果中
     * @param array $certs 如果需要验证证书则传入证书地址
     * @return array ['code'=>200, 'msg'=>'success', 'response'=>['header'=>[], 'data'=>""]]
     */
    function CurlGet($url, $data = [], $header = [], $isSsl = false, $isOutHeader = false, $certs = ['apiclient_cert' => '', 'apiclient_key' => '', 'cacert' => ''])
    {
        $urlInfo = parse_url($url);
        $eol = array_key_exists('query', $urlInfo) ? '&' : '?';
        $url = trim($url, '?') . $eol . (is_string($data) ? $data : http_build_query($data));
        $ch = curl_init();
        if (!empty($header)) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        }
        curl_setopt($ch, CURLOPT_URL, $url);
        $isOutHeader ? curl_setopt($ch, CURLOPT_HEADER, true) : curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);

        if ($isSsl) {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);//证书检查
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'pem');
//			curl_setopt($ch,CURLOPT_SSLCERT,dirname(__FILE__).'/../cert/apiclient_cert.pem');
            curl_setopt($ch, CURLOPT_SSLCERT, $certs['apiclient_cert']);
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'pem');
//			curl_setopt($ch,CURLOPT_SSLKEY,dirname(__FILE__).'/../cert/apiclient_key.pem');
            curl_setopt($ch, CURLOPT_SSLCERT, $certs['apiclient_key']);
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'pem');
//			curl_setopt($ch,CURLOPT_CAINFO,dirname(__FILE__).'/../cert/cacert.pem');
            curl_setopt($ch, CURLOPT_SSLCERT, $certs['cacert']);
        } else {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        }

        try {
            $reponse = curl_exec($ch);
            if (curl_errno($ch)) {
                throw new \Exception(curl_error($ch), 0);
            } else {
                $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                if (200 !== $httpStatusCode) {
                    throw new \Exception($reponse, $httpStatusCode);
                }
            }
            curl_close($ch);

            if ($isOutHeader) {
                $headerData = [];
                list($headerStr, $responseData) = explode("\r\n\r\n", $reponse, 2);
                $headerDataList = explode("\r\n", $headerStr);
                $isStart = true;
                foreach ($headerDataList as $headerLine) {
                    if ($isStart) {
                        $isStart = false;
                        continue;
                    }

                    list($headerKey, $headerValue) = explode(":", $headerLine);
                    if (array_key_exists(trim($headerKey))) {
                        $headerData[trim($headerKey)] = $headerData[trim($headerKey)]. "," . trim($headerValue);
                    } else {
                        $headerData[trim($headerKey)] = trim($headerValue);
                    }
                }
            } else {
                $headerData = [];
                $responseData = $reponse;
            }

            return ['code' => $httpStatusCode, 'msg' => 'success', 'response' => ['header' => $headerData, 'data' => $responseData]];
        } catch (\Throwable $exception) {
            curl_close($ch);
            return ['code' => $exception->getCode(), 'msg' => $exception->getMessage(), 'response' => ['header' => [], 'data' => ""]];
        }
    }
}

if (!function_exists('CurlPut')) {
    /**
     * CURL PUT请求
     * @param $url 请求路径
     * @param $data 请求数据,不同的请求方式使用不同的格式，如果需要字符串而传入了对象或数组则会自动调用json序列化为字符串，如果contentType='multipart/form-data'时如果需要传输文件，则请在参数中以media为key,值为数组，数组中存放上传文件的二进制数据,数组中可以有多个文件 例如: param=['p1'=>'t1','media'=>['file1'=>file_get_content($path1), 'file2'=>file_get_content($path2),]]
     * @param array $header 每一个元素就是一个header项,例如： '"Content-Type: application/x-www-form-urlencoded;'
     * @param bool $isSsl 是否进行SSL证书验证,默认FALSE
     * @param string $contentType 数据传输类型,支持:multipart/form-data;application/json;application/x-www-form-urlencoded;text/html;text/plain;
     * @param bool $isOutHeader 是否解析响应头,如果传入false则会解析header的key=>value到响应结果中
     * @param array $certs 如果需要验证证书则传入证书地址
     * @return array ['code'=>200, 'msg'=>'success', 'response'=>['header'=>[], 'data'=>""]]
     */
    function CurlPut($url, $data, $header = [], $isSsl = false, $contentType = "application/x-www-form-urlencoded", $isOutHeader = false, $certs = ['apiclient_cert' => '', 'apiclient_key' => '', 'cacert' => ''])
    {
        // 所有允许访问的类型
        $contentTypeList = [
            "multipart/form-data" => function ($param) {
                $delimiter = uniqid();
                $data = '';
                $eol = "\r\n";
                $mediaList = array_key_exists('media', $param) ? (array)$param['media'] : [];
                unset($param['media']);
                $fromDataList = [];
                foreach ($param as $key => $value) {
                    $fromDataList[$key] = is_string($value) ? $value : json_encode($value);
                }

                foreach ($fromDataList as $name => $content) {
                    $data .= "--" . $delimiter . $eol
                        . 'Content-Disposition: form-data; name="' . $name . "\"" . $eol . $eol
                        . $content . $eol;
                }

                foreach ($mediaList as $key => $value) {
                    $data .= "--" . $delimiter . $eol
                        . 'Content-Disposition: form-data; name="' . $key . '"; filename="' . $value['fileName'] . '"' . $eol
                        . 'Content-Type:application/octet-stream' . $eol . $eol;
                    $data .= $value['content'] . $eol;
                    $data .= "--" . $delimiter . "--" . $eol;
                }

                return ['delimiter' => $delimiter, 'data' => $data];
            },
            "application/json" => function ($data) {
                return json_encode($data);
            },
            "application/x-www-form-urlencoded" => function ($data) {
                return is_string($data) ? $data : http_build_query($data);
            },
            "text/html" => function ($data) {
                return is_string($data) ? $data : json_encode($data);
            },
            "text/plain" => function ($data) {
                return is_string($data) ? $data : json_encode($data);
            }
        ];

        if (!array_key_exists($contentType, $contentTypeList)) {
            return ['code' => 500, 'msg' => '请求类型不支持', 'response' => ['header' => [], 'data' => ""]];
        }

        $data = $contentTypeList[$contentType]($data);
        $isOutHeader = (boolean)$isOutHeader;
        $ch = curl_init();
        $timeout = 600;

        if ($contentType == "multipart/form-data") {
            $header[] = "Content-Type: multipart/form-data; boundary=" . $data['delimiter'];
            $data = $data['data'];
        } else {
            $header[] = "Content-Type: " . $contentType . "; charset=utf-8";
        }

        $header[] = "Content-Length: " . strlen($data);
        $header[] = 'Cache-Control: no-cache';
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
        curl_setopt($ch, CURLOPT_URL, $url);
//        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        $isOutHeader ? curl_setopt($ch, CURLOPT_HEADER, true) : curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
        if ($isSsl) {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);//证书检查
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'pem');
//			curl_setopt($ch,CURLOPT_SSLCERT,dirname(__FILE__).'/../cert/apiclient_cert.pem');
            curl_setopt($ch, CURLOPT_SSLCERT, $certs['apiclient_cert']);
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'pem');
//			curl_setopt($ch,CURLOPT_SSLKEY,dirname(__FILE__).'/../cert/apiclient_key.pem');
            curl_setopt($ch, CURLOPT_SSLCERT, $certs['apiclient_key']);
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'pem');
//			curl_setopt($ch,CURLOPT_CAINFO,dirname(__FILE__).'/../cert/cacert.pem');
            curl_setopt($ch, CURLOPT_SSLCERT, $certs['cacert']);
        } else {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        }

        try {
            $reponse = curl_exec($ch);
            if (curl_errno($ch)) {
                throw new \Exception(curl_error($ch), 0);
            } else {
                $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                if (!in_array($httpStatusCode, [200, 201])) {
                    throw new \Exception($reponse, $httpStatusCode);
                }
                $httpStatusCode = 200;
            }
            curl_close($ch);

            if ($isOutHeader) {
                $headerData = [];
                list($headerStr, $responseData) = explode("\r\n\r\n", $reponse, 2);
                $headerDataList = explode("\r\n", $headerStr);
                $isStart = true;
                foreach ($headerDataList as $headerLine) {
                    if ($isStart) {
                        $isStart = false;
                        continue;
                    }

                    list($headerKey, $headerValue) = explode(":", $headerLine);
                    if (array_key_exists(trim($headerKey))) {
                        $headerData[trim($headerKey)] = $headerData[trim($headerKey)]. "," . trim($headerValue);
                    } else {
                        $headerData[trim($headerKey)] = trim($headerValue);
                    }
                }
            } else {
                $headerData = [];
                $responseData = $reponse;
            }

            return ['code' => $httpStatusCode, 'msg' => 'success', 'response' => ['header' => $headerData, 'data' => $responseData]];
        } catch (\Throwable $exception) {
            curl_close($ch);
            return ['code' => $exception->getCode(), 'msg' => $exception->getMessage(), 'response' => ['header' => [], 'data' => ""]];
        }
    }
}

if (!function_exists('CurlDelete')) {
    /**
     * CURL DELETE请求
     * @param $url 请求路径
     * @param $data 请求数据,不同的请求方式使用不同的格式，如果需要字符串而传入了对象或数组则会自动调用json序列化为字符串
     * @param array $header 每一个元素就是一个header项,例如： '"Content-Type: application/x-www-form-urlencoded;'
     * @param bool $isSsl 是否进行SSL证书验证,默认FALSE
     * @param bool $isOutHeader 是否解析响应头,如果传入false则会解析header的key=>value到响应结果中
     * @param array $certs 如果需要验证证书则传入证书地址
     * @return array ['code'=>200, 'msg'=>'success', 'response'=>['header'=>[], 'data'=>""]]
     */
    function CurlDelete($url, $data = [], $header = [], $isSsl = false, $isOutHeader = false, $certs = ['apiclient_cert' => '', 'apiclient_key' => '', 'cacert' => ''])
    {
        $urlInfo = parse_url($url);
        $eol = array_key_exists('query', $urlInfo) ? '&' : '?';
        $url = trim($url, '?') . $eol . (is_string($data) ? $data : http_build_query($data));
        $ch = curl_init();
        if (!empty($header)) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        }
        curl_setopt($ch, CURLOPT_URL, $url);
        $isOutHeader ? curl_setopt($ch, CURLOPT_HEADER, true) : curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');

        if ($isSsl) {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);//证书检查
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'pem');
//			curl_setopt($ch,CURLOPT_SSLCERT,dirname(__FILE__).'/../cert/apiclient_cert.pem');
            curl_setopt($ch, CURLOPT_SSLCERT, $certs['apiclient_cert']);
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'pem');
//			curl_setopt($ch,CURLOPT_SSLKEY,dirname(__FILE__).'/../cert/apiclient_key.pem');
            curl_setopt($ch, CURLOPT_SSLCERT, $certs['apiclient_key']);
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'pem');
//			curl_setopt($ch,CURLOPT_CAINFO,dirname(__FILE__).'/../cert/cacert.pem');
            curl_setopt($ch, CURLOPT_SSLCERT, $certs['cacert']);
        } else {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        }

        try {
            $reponse = curl_exec($ch);
            if (curl_errno($ch)) {
                throw new \Exception(curl_error($ch), 0);
            } else {
                $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                if (!in_array($httpStatusCode, [200,201,202])) {
                    throw new \Exception($reponse, $httpStatusCode);
                }
                $httpStatusCode = 200;
            }
            curl_close($ch);

            if ($isOutHeader) {
                $headerData = [];
                list($headerStr, $responseData) = explode("\r\n\r\n", $reponse, 2);
                $headerDataList = explode("\r\n", $headerStr);
                $isStart = true;
                foreach ($headerDataList as $headerLine) {
                    if ($isStart) {
                        $isStart = false;
                        continue;
                    }

                    list($headerKey, $headerValue) = explode(":", $headerLine);
                    if (array_key_exists(trim($headerKey))) {
                        $headerData[trim($headerKey)] = $headerData[trim($headerKey)]. "," . trim($headerValue);
                    } else {
                        $headerData[trim($headerKey)] = trim($headerValue);
                    }
                }
            } else {
                $headerData = [];
                $responseData = $reponse;
            }

            return ['code' => $httpStatusCode, 'msg' => 'success', 'response' => ['header' => $headerData, 'data' => $responseData]];
        } catch (\Throwable $exception) {
            curl_close($ch);
            return ['code' => $exception->getCode(), 'msg' => $exception->getMessage(), 'response' => ['header' => [], 'data' => ""]];
        }
    }
}

if (!function_exists('DeleteDir')) {
    /**
     * 删除文件夹
     * @param $dir 文件夹路径
     * @return bool
     */
    function DeleteDir($dir)
    {
        if (!is_dir($dir)) {
            return false;
        }
        try {
            $dh = opendir($dir);
            while ($file = readdir($dh)) {
                if ($file != "." && $file != "..") {
                    $fullPath = $dir . "/" . $file;
                    if (!is_dir($fullPath)) {
                        @unlink($fullPath);
                    } else {
                        DeleteDir($fullPath);
                    }
                }
            }
            closedir($dh);
            if (@rmdir($dir)) {
                return true;
            } else {
                return false;
            }
        } catch (\Throwable $exception) {
            return false;
        }
    }
}

if (!function_exists('IsEmail')) {
    /**
     * 判定是否是邮箱地址
     * @param $address
     * @return bool
     */
    function IsEmail($address)
    {
        return filter_var($address, FILTER_VALIDATE_EMAIL) === false ? false : true;
    }
}

if (!function_exists('GetClientIp')) {
    /**
     * 获取客户端IP
     * @return bool|mixed|string
     */
    function GetClientIp()
    {
        $ip = false;
        //客户端IP 或 NONE
        if (!empty($_SERVER["HTTP_CLIENT_IP"])) {
            $ip = $_SERVER["HTTP_CLIENT_IP"];
        }

        //多重代理服务器下的客户端真实IP地址（可能伪造）,如果没有使用代理，此字段为空
        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ips = explode(", ", $_SERVER['HTTP_X_FORWARDED_FOR']);
            if ($ip) {
                array_unshift($ips, $ip);
                $ip = false;
            }
            for ($i = 0; $i < count($ips); $i++) {
                if (!preg_match("/^(10|172.16|192.168)./", $ips[$i])) {
                    $ip = $ips[$i];
                    break;
                }
            }
        }

        //客户端IP 或 (最后一个)代理服务器 IP
        return ($ip ? $ip : $_SERVER['REMOTE_ADDR'] ?? "");
    }
}

if (!function_exists('request')) {
    /**
     * 获取当前请求实例
     *
     * @param array|string|null $key
     * @param mixed $default
     * @return \Illuminate\Http\Request|string|array
     */
    function request($key = null, $default = null)
    {
        if (is_null($key)) {
            return app('request');
        }

        if (is_array($key)) {
            return app('request')->only($key);
        }

        $value = app('request')->__get($key);

        return is_null($value) ? value($default) : $value;
    }
}

if (!function_exists('UploadFile')) {
    /**
     * 上传文件
     * @param $fileList 文件列表 reqeust()->allFiles();获取
     * @param string $toPath 存储文件目录
     * @param array $allowExtList 允许上传文件后缀列表
     * @param string $saveFileNames 存储文件名称，当名称为空时则会自动获取上传文件的名字
     * @param bool $isCoverFile 是否覆盖已存在的文件
     * @param bool $isMd5FileName 是否对文件名称进行MD5摘要
     * @param int $allowUploadFileSize 允许上传文件的大小单位为B，默认是10M=10485760
     * @param string $baseSavePath 存储文件的根目录
     * @return array
     */
    function UploadFile($fileList, $toPath = '', $allowExtList = [], $saveFileNames = '', $isCoverFile = false, $isMd5FileName = true, $allowUploadFileSize = 10485760, $baseSavePath = "")
    {
        if (!is_array($fileList)) {
            return ['code' => 1, 'msg' => 'params must is array!'];
        }

        if ($baseSavePath == "") {
            $baseSavePath = storage_path("app/uploads");
        }
        $baseSavePath = rtrim($baseSavePath, '/');

        if ($toPath == '') {
            $toPath = date("Ymd");
        }
        $toPath = trim($toPath, '/');

        $savePath = $baseSavePath . '/' . $toPath;
        if (!is_dir($savePath) && !CreateDir($savePath)) {
            return ['code' => RETURN_FAIL, 'msg' => '创建存储文件夹失败', 'data' => []];
        }

        $relFileList = [];

        $deleteAllRelFileList = function ($relFileList) {
            foreach ($relFileList as $tempFile) {
                @unlink($tempFile['saveFilePath']);
            }
        };

        // 覆盖文件列表
        $coverFileList = [];
        $deleteAllCoverFileList = function ($coverFileList) {
            foreach ($coverFileList as $tempFile) {
                @unlink($tempFile . '.temp');
            }
        };

        // 恢复覆盖文件
        $recoveryAllCoverFileList = function ($coverFileList) {
            foreach ($coverFileList as $tempFile) {
                @rename($tempFile . '.temp', $tempFile);
            }
        };

        //处理允许上传后缀
        array_walk($allowExtList, function (&$ext) {
            $ext = strtolower($ext);
        });

        try {
            foreach ($fileList as $fileObj) {
                if (!is_array($fileObj)) {
                    $fileObj = [$fileObj];
                }

                foreach ($fileObj as $file) {
                    $saveFileName = $saveFileNames;
                    //TODO: 兼容各种多文件上传
                    if (!$file->isValid()) {
                        $deleteAllRelFileList($relFileList);
                        return ['code' => 2, 'msg' => '文件上传不完整', 'data' => []];
                    }

                    $fileExt = $file->getClientOriginalExtension();
                    if (!in_array(strtolower($fileExt), $allowExtList, true)) {
                        $deleteAllRelFileList($relFileList);
                        return ['code' => 3, 'msg' => '非法的文件后缀:' . $fileExt, 'data' => []];
                    }

                    $fileSize = $file->getClientSize();
                    if ($fileSize > $allowUploadFileSize) {
                        $deleteAllRelFileList($relFileList);
                        return ['code' => 4, 'msg' => '文件上传大小为:' . $fileSize . 'B,允许上传大小为:' . $allowUploadFileSize . "B", 'data' => []];
                    }

                    if ($saveFileName == '') {
                        $saveFileName = pathinfo($file->getClientOriginalName())['filename'] ?? $file->getClientOriginalName();
                    }

                    $index = 0;
                    // 最多尝试100次重复文件
                    $tempSaveFileName = $saveFileName;
                    while ($index < 100) {
                        if ($isMd5FileName) {
                            $saveFileName = md5($saveFileName);
                        }

                        $tempFileName = $savePath . '/' . $saveFileName . '.' . $fileExt;
                        if (file_exists($tempFileName)) {
                            if ($isCoverFile) {
                                @rename($tempFileName, $tempFileName . '.temp');
                                $coverFileList[] = $tempFileName;
                                break;
                            }

                            $index++;
                            $saveFileName = $tempSaveFileName . "_" . $index;
                            continue;
                        }
                        break;
                    }
                    $saveFileName = $saveFileName . '.' . $fileExt;
                    $file->move($savePath, $saveFileName);
                    $relFileList[$saveFileName] = [
                        'saveFilePath' => $savePath . '/' . $saveFileName,
                        'baseSavePath' => $baseSavePath,
                        'toPath' => $toPath,
                        'saveFileName' => $saveFileName,
                        'fileName' => $file->getClientOriginalName(),
                        'mimeType' => $file->getClientMimeType()
                    ];
                }
            }
            $deleteAllCoverFileList($coverFileList);
            return ['code' => RETURN_SUCCESS, 'msg' => '上传文件成功', 'data' => $relFileList];
        } catch (\Throwable $exception) {
            $deleteAllRelFileList($relFileList);
            $recoveryAllCoverFileList($coverFileList);
            return ['code' => $exception->getCode(), 'msg' => $exception->getMessage(), 'data' => []];
        }
    }
}

if (!function_exists('UploadImg')) {
    /**
     * 上传图片
     * @param string $uploadFileObj 文件对象或文件对象数组或者Request对象，也可以不传则会默认获取所有上传文件
     * @param string $toPath 上传文件存储地址
     * @param array $allowExtList 允许上传文件后缀
     * @param string $baseSavePath 上传文件基础目录
     * @param bool $isAddDateDir 是否自动添加日期分割目录
     * @param int $allowUploadFileSize 允许上传文件的大小单位为B，默认是10M=10485760
     * @return array
     */
    function UploadImg($uploadFileObj = '', $toPath = 'images', $allowExtList = ['png', 'jpg', 'jpeg', 'gif', 'bmp'], $baseSavePath = "", $isAddDateDir = true, $allowUploadFileSize = 10485760)
    {
        if ($baseSavePath == "") {
            $baseSavePath = base_path("public");
        }

        if ($isAddDateDir) {
            $toPath = trim($toPath, '/') . '/' . date("Ymd");
        }

        if ($uploadFileObj == '') {
            $uploadFileObj = request()->allFiles();
        }

        if ($uploadFileObj instanceof \Illuminate\Http\Request) {
            $uploadFileObj = $uploadFileObj->allFiles();
        } else if (!is_array($uploadFileObj)) {
            $uploadFileObj = [$uploadFileObj];
        }

        return UploadFile($uploadFileObj, $toPath, $allowExtList, '', false, true, $allowUploadFileSize, $baseSavePath);
    }
}

if (!function_exists('UploadCommonFile')) {
    /**
     * 上传常用文件
     * @param string $uploadFileObj 文件对象或文件对象数组或者Request对象，也可以不传则会默认获取所有上传文件
     * @param string $toPath 上传文件存储地址
     * @param array $allowExtList 允许上传文件后缀
     * @param string $baseSavePath 上传文件基础目录
     * @param bool $isAddDateDir 是否自动添加日期分割目录
     * @param int $allowUploadFileSize 允许上传文件的大小单位为B，默认是50M=52428800
     * @return array
     */
    function UploadCommonFile($uploadFileObj = '', $toPath = 'files', $allowExtList = ['fot', 'fon', 'txt', 'doc', 'rtf', 'zip', 'rar', 'xls', 'xlsx', 'pdf', 'docx', 'ppt', 'pptx', 'mp3', 'mp4', 'avi', 'png', 'jpg', 'jpeg', 'gif'], $baseSavePath = "", $isAddDateDir = true, $allowUploadFileSize = 52428800)
    {
        if ($baseSavePath == "") {
            $baseSavePath = base_path("public");
        }

        if ($isAddDateDir) {
            $toPath = trim($toPath, '/') . '/' . date("Ymd");
        }

        if ($uploadFileObj == '') {
            $uploadFileObj = request()->allFiles();
        }

        if ($uploadFileObj instanceof \Illuminate\Http\Request) {
            $uploadFileObj = $uploadFileObj->allFiles();
        } else if (!is_array($uploadFileObj)) {
            $uploadFileObj = [$uploadFileObj];
        }

        return UploadFile($uploadFileObj, $toPath, $allowExtList, '', false, true, $allowUploadFileSize, $baseSavePath);
    }
}

if (!function_exists('CreateTree')) {
    /**
     * 生成树结构
     * @param array $data 数据源
     * @param int $pid 定级的父级统一值，默认为0
     * @param string $pidFiled 每个元素的父级字段名称PID(此字段必须与$idFiled的性质相同都表示唯一标识)
     * @param string $idFiled 每个元素的主键字段名称ID
     * @param string $childArrFiled 返回子集的KEY名称
     * @return array
     */
    function CreateTree($data = [], $pid = 0, $pidFiled = 'parent_id', $idFiled = 'id', $childArrFiled = 'child')
    {
        if (!is_array($data)) {
            return ['code' => 1, 'msg' => 'data must is array.', 'data' => []];
        }

        try {
            $tempData = [];
            foreach ($data as $item) {
                $item[$childArrFiled] = [];
                $tempData[$item[$idFiled]] = $item;
            }

            $relData = [];
            foreach ($tempData as $key => &$value) {
                if ($value[$pidFiled] === $pid) {
                    $relData[$key] = &$value;
                } else {
                    $tempData[$value[$pidFiled]][$childArrFiled][] = &$value;
                }
            }
            ksort($relData);
            return ['code' => RETURN_SUCCESS, 'msg' => 'SUCCESS', 'data' => array_values($relData)];
        } catch (\Throwable $exception) {
            return ['code' => $exception->getCode(), 'msg' => $exception->getMessage(), 'data' => []];
        }
    }
}

if (!function_exists('IsMobilePhone')) {
    /**
     * 检测是否是手机号
     *
     * @param [type] $phone 手机号
     * @return boolean
     */
    function IsMobilePhone($phone)
    {
        $search = '/^0?1[0-9]\d{9}$/';
        if (preg_match($search, $phone)) {
            return true;
        } else {
            return false;
        }
    }
}

if (!function_exists('IsIDC')) {
    /**
     * 判断是否是身份证
     *
     * @param [type] $idc 身份证ID
     * @return boolean
     */
    function IsIDC($idc)
    {
        $id = strtoupper($idc);
        $regx = "/(^\d{15}$)|(^\d{17}([0-9]|X)$)/";
        $arr_split = [];
        if (!preg_match($regx, $id)) {
            return false;
        }

        if (15 == strlen($id)) //检查15位
        {
            $regx = "/^(\d{6})+(\d{2})+(\d{2})+(\d{2})+(\d{3})$/";
            preg_match($regx, $id, $arr_split);
            //检查生日日期是否正确
            $dtm_birth = "19" . $arr_split[2] . '/' . $arr_split[3] . '/' . $arr_split[4];
            if (!strtotime($dtm_birth)) {
                return false;
            } else {
                return true;
            }
        }

        if (18 == strlen($id)) {
            $regx = "/^(\d{6})+(\d{4})+(\d{2})+(\d{2})+(\d{3})([0-9]|X)$/";
            preg_match($regx, $id, $arr_split);
            $dtm_birth = $arr_split[2] . '/' . $arr_split[3] . '/' . $arr_split[4];
            if (!strtotime($dtm_birth)) {
                return false;
            } else {
                //检验18位身份证的校验码是否正确。
                //校验位按照ISO 7064:1983.MOD 11-2的规定生成，X可以认为是数字10。
                $arr_int = array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2);
                $arr_ch = array('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2');
                $sign = 0;
                for ($i = 0; $i < 17; $i++) {
                    $b = (int)$id[$i];
                    $w = $arr_int[$i];
                    $sign += $b * $w;
                }
                $n = $sign % 11;
                $val_num = $arr_ch[$n];
                if ($val_num != substr($id, 17, 1)) {
                    return false;
                } else {
                    return true;
                }
            }
        }
    }
}

if (!function_exists('IDCInfo')) {
    /**
     * 获取身份证信息
     *
     * @param [type] $IDCard 身份则ID
     * @return array ['birth'=>'2022-05-19', 'sex'=>0] birth:生日 sex:性别0:女 1:男
     */
    function IDCInfo($IDCard)
    {
        $bir = substr($IDCard, 6, 8);
        $data['birth'] = substr($bir, 0, 4) . '-' . substr($bir, 4, 2) . '-' . substr($bir, 6, 2);
        $data['sex'] = substr($IDCard, -2, 1) % 2;
        return $data;
    }
}

if (!function_exists('CreateDir')) {
    /**
     * 创建文件夹
     * @param $dirPath 文件夹路径
     * @param int $chmod 文件权限
     * @return bool
     */
    function CreateDir($dirPath, $chmod = 0777)
    {
        try {
            return is_dir($dirPath) or (CreateDir(dirname($dirPath), $chmod) && mkdir($dirPath, $chmod) && chmod($dirPath, $chmod));
        } catch (\Throwable $exception) {
            return false;
        }
    }
}

if (!function_exists('IsCli')) {
    /**
     * 判断当前模式是否是CLI命令行模式
     * @return bool
     */
    function IsCli() {
        return php_sapi_name() === 'cli';
    }
}